﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WorkItemChangedEventProcessorBase.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the WorkItemChangedEventProcessorBase type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace ScrumForTeamSystem.EventService.Infrastructure
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Interfaces;

    using Microsoft.TeamFoundation.WorkItemTracking.Client;

    using ScrumForTeamSystem.EventService.Infrastructure.Properties;

    /// <summary>
    /// Initializes instance of WorkItemChangedEventProcessorBase&lt;TRule&gt;
    /// </summary>
    /// <typeparam name="TRule">The type of the rule.</typeparam>
    public abstract class WorkItemChangedEventProcessorBase<TRule> : ITfsEventProcessor, IMessageProducer
    {
        /// <summary>
        /// The work item store repository service.
        /// </summary>
        private readonly IWorkItemStoreRepository workItemStoreRepository;

        /// <summary>
        /// The rules collection.
        /// </summary>
        private IRulesCollection<TRule> rulesCollection;

        /// <summary>
        /// The is first run flag.
        /// </summary>
        private bool isFirstRun;

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkItemChangedEventProcessorBase{TRule}"/> class.
        /// </summary>
        /// <param name="workItemStoreRepository">The work item store repository.</param>
        protected WorkItemChangedEventProcessorBase(IWorkItemStoreRepository workItemStoreRepository)
        {
            this.workItemStoreRepository = workItemStoreRepository;
            this.isFirstRun = true;
        }

        /// <summary>
        /// Occurs when [Message] event raised.
        /// </summary>
        public event EventHandler<ActionEventArgs> Message;

        /// <summary>
        /// Occurs when [exception Message] event raised.
        /// </summary>
        public event EventHandler<ExceptionActionEventArgs> ExceptionMessage;

        /// <summary>
        /// Processes the specified event instance.
        /// </summary>
        /// <param name="eventInstance">The event instance.</param>
        public virtual void Process(ITfsEvent eventInstance)
        {
            if (eventInstance == null)
            {
                throw new ArgumentNullException("eventInstance");
            }

            if (this.isFirstRun)
            {
                this.rulesCollection = this.LoadRules();
                this.isFirstRun = false;
            }

            WorkItemChangedEvent workItemChangedEvent;

            if (!GlobalHelpers.TryCastEvent(eventInstance, out workItemChangedEvent))
            {
                return;
            }

            var matchedRules = this.rulesCollection.GetMatchingRules(workItemChangedEvent);

            var hasMatchedRules = matchedRules.Count() != 0;
            var hasLinkChanges = workItemChangedEvent.HasLinkChanges();

            if (!hasMatchedRules && !hasLinkChanges)
            {
                return;
            }

            this.OnMessage(this, Resources.String001);

            WorkItemStore workItemStore;

            if (!this.TryGetWorkItemStore(eventInstance.ServerIdentity.Url, out workItemStore))
            {
                this.OnMessage(this, Resources.String002);
                return;
            }

            if (hasMatchedRules)
            {
                this.ProcessMatchedRules(workItemChangedEvent, workItemStore, matchedRules);
            }

            if (hasLinkChanges)
            {
                this.ProcessLinkChanges(workItemChangedEvent, workItemStore, this.rulesCollection);
            }

            this.OnMessage(this, Resources.String003);
        }

        /// <summary>
        /// Asserts the event and store are not null.
        /// </summary>
        /// <param name="workItemChangedEvent">The work item changed event.</param>
        /// <param name="workItemStore">The work item store.</param>
        protected static void AssertEventAndStoreAreNotNull(WorkItemChangedEvent workItemChangedEvent, WorkItemStore workItemStore)
        {
            if (workItemChangedEvent == null)
            {
                throw new ArgumentNullException("workItemChangedEvent");
            }

            if (workItemStore == null)
            {
                throw new ArgumentNullException("workItemStore");
            }
        }

        /// <summary>
        /// Loads the rules.
        /// </summary>
        /// <returns>A collection of rules.</returns>
        protected abstract IRulesCollection<TRule> LoadRules();

        /// <summary>
        /// Processes the links changes.
        /// </summary>
        /// <param name="workItemChangedEvent">The work item changed event.</param>
        /// <param name="workItemStore">The work item store.</param>
        /// <param name="allRules">All rules.</param>
        protected abstract void ProcessLinkChanges(WorkItemChangedEvent workItemChangedEvent, WorkItemStore workItemStore, IEnumerable<TRule> allRules);

        /// <summary>
        /// Processes the matched rules.
        /// </summary>
        /// <param name="workItemChangedEvent">The work item changed event.</param>
        /// <param name="workItemStore">The work item store.</param>
        /// <param name="matchedRules">The matched rules.</param>
        protected abstract void ProcessMatchedRules(WorkItemChangedEvent workItemChangedEvent, WorkItemStore workItemStore, IEnumerable<TRule> matchedRules);

        /// <summary>
        /// Called on [exception message].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="exception">The exception.</param>
        protected void OnExceptionMessage(object sender, Exception exception)
        {
            if (this.ExceptionMessage == null)
            {
                throw exception;
            }

            this.ExceptionMessage(sender, new ExceptionActionEventArgs(exception));
        }

        /// <summary>
        /// Called when [process message].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="message">The message.</param>
        protected void OnMessage(object sender, string message)
        {
            if (this.Message != null)
            {
                this.Message(sender, new ActionEventArgs(message));
            }
        }

        /// <summary>
        /// Tries to get the work item store.
        /// </summary>
        /// <param name="tfsAddress">The TFS address.</param>
        /// <param name="workItemStore">The work item store.</param>
        /// <returns><c>True</c> if the work item store is retrived; otherwise <c>false</c></returns>
        private bool TryGetWorkItemStore(string tfsAddress, out WorkItemStore workItemStore)
        {
            workItemStore = this.workItemStoreRepository.GetWorkItemStore(tfsAddress);

            return workItemStore != null;
        }
    }
}
